const util = require('util');
const Web3 = require('web3');
const utils = require('j7/utils');
const bcutils = require('j7/bcutils');
const event = require('j7/event');
const sync = require("j7/sync");
const log = require("j7/log");
const contract = require('common/contract');
const bcconst = require('common/bcconst');
const metaFactory = require('./metadata/factory');

class BlockChain {

  constructor(netId) {
    this.actived = false;
    this.netId = netId;
    this.lastQueryTime = utils.getUtcTime();
    this.queryLockTimes = 0;
    this.currBlockNumber = 0;
    this.refreshCond = new sync.Cond();
    this.lastRefreshTime = utils.getUtcTime();
    setTimeout(this.refreshBlockNumber.bind(this), 1000 * 0.01);
  }

  async initInstance(user, address, jsonUrl) {
    const json = utils.readJsonFromFile(jsonUrl);
    return new this.web3.eth.Contract(
      json.abi,
      address,
      { from: user }
    );
  }

  async init() {
    this.web3Conf = metaFactory.getWeb3Conf(this.netId);
    this.contractsConf = metaFactory.getContractsConf(this.netId);
    this.netDir = metaFactory.getNetDir(this.netId);

    this.web3 = new Web3(this.getRpcUrl());
    this.web3.eth.handleRevert = true;
    this.web3.eth.accounts.wallet.add(this.getPrivateKey());
    for (const data of this.contractsConf) {
      try {
        this[`${data.name}Instance`] = await this.initInstance
        (this.getUserAddress(), data.address, this.netDir + data.json);
      } catch (err) {
        if (utils.isOnlineEnv()) {
          throw new Error(err);
        } else {
          if (data.name == 'AridropNft') {
            log.warning('load contract file error:' + err);
          }
        }
      }
    }
    const chainId = await this.web3.eth.getChainId();
    if (chainId != this.netId) {
      throw new Error(util.format('net id error %s %s',
                                  chainId,
                                  this.netId
                                 ));
      log.warning(util.format('net id error %s %s',
                              chainId,
                              this.netId
                             ));
    }
    log.info(util.format('local.net_id:%s remote_net_id:%s',
                         this.netId,
                         chainId
                        ));
    {
      await this.mustBeActive();
      const netId = this.getNetId();
      console.log('net_id:', netId, ' blockNumber:', this.getCurrBlockNumber(),
                  ' handleRevert:', this.web3.eth.handleRevert, ' isOnlineEnv:', utils.isOnlineEnv());
    }
  }

  async mustBeActive() {
    while (!this.actived) {
      await utils.sleep(1000);
    }
  }

  getNetId() {
    return this.netId;
  }

  getRpcUrl() {
    return this.web3Conf['block_server'];
  }

  getUserAddress() {
    return this.web3Conf['user_address'];
  }

  getPrivateKey() {
    return this.web3Conf['private_key'];
  }

  getContractByName(name) {
    let contract = null;
    this.contractsConf.forEach((item) => {
      if (item['name'] == name) {
        contract = item;
      }
    });
    return contract;
  }

  getCurrBlockNumber() {
    return this.currBlockNumber;
  }

  async lockQuery() {
    while (this.queryLockTimes > 3) {
      await utils.sleep(100 + utils.randRange(10, 100));
    }
    ++this.queryLockTimes;
    this.lastQueryTime = utils.getUtcTime();
  }

  async unlockQuery() {
    await utils.sleep(10 + utils.randRange(10, 50));
    --this.queryLockTimes;
  }

  isAddress(address) {
    return this.web3.utils.isAddress(address);
  }

  async refreshBlockNumber() {
    const logHead = ' refreshBlockNumber:';
    while (true) {
      try {
        this.currBlockNumber = await this.web3.eth.getBlockNumber();
        this.actived = true;
        this.lastRefreshTime = utils.getUtcTime();
        console.log('currBlockNumber', this.currBlockNumber);
      } catch (e) {
        this.actived = false;
        log.warning(util.format('%s err:%s',
                                logHead,
                                e
                               ));
      }
      await this.refreshCond.wait(1000 * 3);
    }
  }

  async getBlockData(blockNumber) {
    return await this.web3.eth.getBlock(blockNumber);
  }

}

module.exports = BlockChain;
